<script setup>
import { ref, watch, onMounted, onUnmounted } from 'vue'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
import { UnrealBloomPass } from './UnrealBloomPass.js'
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'
import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js'
import { Line2 } from 'three/examples/jsm/lines/Line2.js'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js'
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js'

import diTuWenLi from './diTuWenLi.png?url'

const container = ref()
const props = defineProps({
  width: {
    type: [String, Number],
    default() {
      return 500
    }
  },
  height: {
    type: [String, Number],
    default() {
      return 300
    }
  }
})

// 加载地图数据 map.value.setData(quanGuoGeo)
// 选择地区 map.value.chooseQuYu(['河南省', '湖北省', '陕西省', '四川省', '广东省'])

let clock = new THREE.Clock()
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(45, props.width / props.height, 0.1, 1000)

const renderer = new THREE.WebGLRenderer({
  antialias: true,
  alpha: true
})
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(props.width, props.height)
renderer.shadowMap.enabled = true

const renderScene = new RenderPass(scene, camera)
const bloomPass = new UnrealBloomPass(new THREE.Vector2(props.width, props.height))
bloomPass.threshold = 0
bloomPass.strength = 0.6
bloomPass.radius = 0
const bloomComposer = new EffectComposer(renderer)
bloomComposer.renderToScreen = false
bloomComposer.addPass(renderScene)
bloomComposer.addPass(bloomPass)

const mixPass = new ShaderPass(
  new THREE.ShaderMaterial({
    uniforms: {
      baseTexture: { value: null },
      bloomTexture: { value: bloomComposer.renderTarget2.texture }
    },
    vertexShader: `varying vec2 vUv;
			void main() {
				vUv = uv;
				gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );

			}`,
    fragmentShader: `uniform sampler2D baseTexture;
			uniform sampler2D bloomTexture;
			varying vec2 vUv;
			void main() {
				gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( bloomTexture, vUv ) );
			}`,
    defines: {}
  }),
  'baseTexture'
)
mixPass.needsSwap = true
const outputPass = new OutputPass()
const finalComposer = new EffectComposer(renderer)
finalComposer.addPass(renderScene)
finalComposer.addPass(mixPass)
finalComposer.addPass(outputPass)

const BLOOM_SCENE = 1
const bloomLayer = new THREE.Layers()
bloomLayer.set(BLOOM_SCENE)
const darkMaterial = new THREE.MeshBasicMaterial({ color: 'black' })
const materials = {}

scene.add(new THREE.AmbientLight(0x060606))
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5)
directionalLight.castShadow = true
directionalLight.shadow.mapSize.width = 1024
directionalLight.shadow.mapSize.height = 1024
directionalLight.shadow.camera.left = 25
directionalLight.shadow.camera.right = -25
directionalLight.shadow.camera.top = 25
directionalLight.shadow.camera.bottom = -25
directionalLight.shadow.camera.near = 0.5
directionalLight.shadow.camera.far = 100
directionalLight.shadow.bias = -0.008
scene.add(directionalLight)

const controls = new OrbitControls(camera, renderer.domElement)
controls.mouseButtons = {
  LEFT: THREE.MOUSE.PAN,
  MIDDLE: THREE.MOUSE.DOLLY
}

let allMesh = new THREE.Group()
let depth = 1
let color = '#02225e' // `#${Math.random().toString().slice(2, 8)}`
// 射线
let raycaster = new THREE.Raycaster()

let getRayObj = (v2) => {
  raycaster.setFromCamera(v2, camera)
  const intersects = raycaster.intersectObjects(allMesh.children, false)
  let obj = intersects.find((o) => {
    if (o.object.isMesh) return true
  })
  return obj && obj.object
}
// 选中地区
const texture = new THREE.TextureLoader().load(diTuWenLi)
texture.wrapS = texture.wrapT = THREE.MirroredRepeatWrapping
texture.repeat.set(0.2, 0.2) // 小于1放大纹理
texture.anisotropy = 10
texture.colorSpace = THREE.SRGBColorSpace
const material = new THREE.MeshStandardMaterial({ map: texture, side: THREE.DoubleSide })
let materialA = new THREE.MeshStandardMaterial({
  color: '#0b2b6a',
  emissive: '#0b2b6a',
  emissiveIntensity: 0.4,
  side: THREE.DoubleSide
})
let select = (name) => {
  end = false
  allMesh.children.forEach((o) => {
    if (name == o.userData.name) {
      o.layers.toggle(BLOOM_SCENE)
      if (o.userData.isLabel || o.userData.isCone || o.userData.isCircle) {
        o.visible = true
      } else {
        o.material[0] = material
        o.userData.clipAction.play()
      }
    }
  })
}
let clearQuYu = () => {
  allMesh.children.forEach((o) => {
    o.layers.set(0)
    if (o.userData.isLabel || o.userData.isCone || o.userData.isCircle) {
      o.visible = false
    } else {
      o.material[0] = materialA
      o.userData.clipAction.stop()
    }
  })
}
function* generat(nameArr) {
  for (let i = 0; i < nameArr.length; i++) {
    select(nameArr[i])
    yield nameArr[i]
  }
}
let gN = null
let end = false
function gDo() {
  let result = gN.next()
  end = result.done
  if (!end) {
    setTimeout(gDo, 1000)
  }
}
let chooseQuYu = (nameArr) => {
  clearQuYu()
  gN = generat(nameArr)
  gDo()
}

let addEvet = () => {
  renderer.domElement.addEventListener('mousedown', (e) => {
    let v2 = new THREE.Vector2()
    v2.x = (event.offsetX / props.width) * 2 - 1
    v2.y = -(event.offsetY / props.height) * 2 + 1
    let obj = getRayObj(v2)

    if (!obj) return
    select(obj.userData.name)

    if (obj?.userData.isMark) {
      let box = new THREE.Box3()
      let zuoBiao = box.setFromObject(obj).getCenter(new THREE.Vector3())
      let v3 = zuoBiao.project(camera)
      var x = Math.round(((v3.x + 1) * props.width) / 2)
      var y = Math.round(((1 - v3.y) * props.height) / 2)
      if (Math.abs(zuoBiao.x) > 1 || Math.abs(zuoBiao.y) > 1) return

      var div = document.createElement('div')
      div.style.position = 'absolute'
      div.style.background = 'rgba(13,41,97, 0.5)'
      div.style.left = x + 'px'
      div.style.top = y + 'px'
      var x = document.createElement('div')
      x.innerHTML = 'x'
      x.style.color = '#aef'
      x.style.cursor = 'pointer'
      x.style.position = 'absolute'
      x.style.top = '10px'
      x.style.right = '10px'
      x.onclick = () => div.remove()
      div.appendChild(x)
      div.insertAdjacentHTML(
        'beforeend',
        `
					<div style="padding: 20px;">
					</div>
				`
      )
      container.value.appendChild(div)
    }
  })
}

let addMark = (item) => {
  if (!item.properties.center) return
  const geometry = new THREE.OctahedronGeometry(0.5, 0)
  const material = new THREE.MeshStandardMaterial({
    color: '#ebff69',
    emissive: '#ebff69',
    emissiveIntensity: 0.3
  })
  const cone = new THREE.Mesh(geometry, material)
  cone.rotateX(-Math.PI / 2)
  cone.position.set(...item.properties.center, 1)
  cone.userData.isMark = true
  cone.castShadow = true
  allMesh.add(cone)
}

let checkMark = (numArr) => {
  let marks = allMesh.children.filter((m) => m.userData.isMark)
  marks.forEach((m) => {
    m.material.visible = false
  })
}

let clearMark = () => {
  let marks = allMesh.children.filter((m) => m.userData.isMark)
  allMesh.remove(...marks)
}
// 绘制
const draw = (item) => {
  if (!item.properties.name) return
  if (item.geometry.type != 'MultiPolygon') return

  // 动画
  let makeDongHua = (obj) => {
    let kfArr = []
    const positionKF = new THREE.VectorKeyframeTrack('.position', [0, 1], [0, 0, 0, 0, 0, 1])
    kfArr[0] = positionKF
    let duration = 4

    if (obj.userData.isCone) {
      const yAxis = new THREE.Vector3(0, 1, 0)

      const q1 = new THREE.Quaternion().copy(obj.rotateY(0).quaternion)
      const q2 = new THREE.Quaternion().copy(obj.rotateY(Math.PI / 2).quaternion)
      const q3 = new THREE.Quaternion().copy(obj.rotateY(Math.PI / 2).quaternion)
      const q4 = new THREE.Quaternion().copy(obj.rotateY(Math.PI / 2).quaternion)
      const q5 = new THREE.Quaternion().copy(obj.rotateY(Math.PI / 2).quaternion)
      const quaternionKF = new THREE.QuaternionKeyframeTrack(
        '.quaternion',
        [0, 1, 2, 3, 4],
        [
          q1.x,
          q1.y,
          q1.z,
          q1.w,
          q2.x,
          q2.y,
          q2.z,
          q2.w,
          q3.x,
          q3.y,
          q3.z,
          q3.w,
          q4.x,
          q4.y,
          q4.z,
          q4.w,
          q5.x,
          q5.y,
          q5.z,
          q5.w
        ]
      )
      kfArr[0] = quaternionKF
    }
    if (obj.userData.isCircle) {
      const scaleKF = new THREE.VectorKeyframeTrack('.scale', [0, 1.5], [0.1, 0.1, 0.1, 1, 1, 1])
      kfArr[0] = scaleKF
      duration = 1.5
    }
    const clip = new THREE.AnimationClip('Action', duration, kfArr)
    let mixer = new THREE.AnimationMixer(obj)
    const clipAction = mixer.clipAction(clip)
    clipAction.clampWhenFinished = true
    clipAction.loop = THREE.LoopOnce
    if (obj.userData.isCone || obj.userData.isCircle) {
      clipAction.loop = THREE.LoopRepeat
      clipAction.play()
    }
    obj.userData.mixer = mixer
    obj.userData.clipAction = clipAction
  }

  function polygon(arr) {
    let points = arr[0].map((a) => new THREE.Vector2(a[0], a[1]))
    const shape = new THREE.Shape()
    shape.setFromPoints(points)

    // 绘制线
    function drwaLine(pointArr) {
      const matLine = new LineMaterial({
        color: 0xffffff,
        linewidth: 0.05,
        worldUnits: true,
        vertexColors: true,
        alphaToCoverage: true
      })
      const positions = []
      const colors = []
      let points = []
      points = arr[0].map((a) => new THREE.Vector3(a[0], a[1], 0))
      const spline = new THREE.CatmullRomCurve3(points)
      const divisions = Math.round(3 * points.length)
      const point = new THREE.Vector3()
      const color = new THREE.Color()
      for (let i = 0, l = divisions; i < l; i++) {
        const t = i / l
        spline.getPoint(t, point)
        positions.push(point.x, point.y, point.z)
        color.set('#5eedf7')
        colors.push(color.r, color.g, color.b)
      }

      const lineGeometry = new LineGeometry()
      lineGeometry.setPositions(positions)
      lineGeometry.setColors(colors)
      let line = new Line2(lineGeometry, matLine)
      line.computeLineDistances()
      line.scale.set(1, 1, 1)
      line.userData.name = item.properties.name
      allMesh.add(line)

      makeDongHua(line)
    }
    drwaLine(points)
    // 洞
    function hole(ar) {
      let points = ar.map((a) => new THREE.Vector2(a[0], a[1]))
      const path = new THREE.Path()
      path.setFromPoints(points)
      shape.holes.push(path)
      drwaLine(points)
    }
    if (arr.length > 1) arr.slice(1).forEach((a) => hole(a))
    // 多边形
    const extrudeSettings = {
      steps: 2,
      depth: -depth,
      bevelEnabled: false
    }
    const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings)
    const material1 = new THREE.MeshStandardMaterial({
      color: '#082554',
      side: THREE.DoubleSide
    })
    const mesh = new THREE.Mesh(geometry, [materialA, material1])
    mesh.castShadow = true
    mesh.receiveShadow = true
    mesh.userData.name = item.properties.name
    allMesh.add(mesh)

    makeDongHua(mesh)
  }
  item.geometry.coordinates.forEach((a) => polygon(a))

  // mark
  // addMark(item)

  // 绘制label
  let canvas = document.createElement('canvas')
  canvas.setAttribute('width', '100')
  canvas.setAttribute('height', '100')
  let ctx = canvas.getContext('2d')
  let lineargradient = ctx.createLinearGradient(0, 50, 100, 50)
  lineargradient.addColorStop(0, 'rgba(0, 0, 0, 0)')
  lineargradient.addColorStop(
    0.5,
    item.properties.name == '河南省' ? '#956d11' : 'rgba(0, 189, 189, 1.0)'
  )
  lineargradient.addColorStop(1, 'rgba(0, 0, 0, 0)')
  ctx.fillStyle = lineargradient
  ctx.fillRect(0, 60, 100, 30)
  ctx.font = '16px math'
  ctx.textAlign = 'center'
  ctx.fillStyle = '#fff'
  ctx.textBaseline = 'bottom'
  let name = item.properties.name
  if (ctx.measureText(name).width < 100) ctx.fillText(name, 50, 85)
  else {
    ctx.font = '14px math'
    ctx.fillText(name.slice(0, name.length / 2), 50, 75)
    ctx.fillText(name.slice(name.length / 2), 50, 90)
  }

  const map = new THREE.TextureLoader().load(canvas.toDataURL())
  const materialText = new THREE.SpriteMaterial({
    map: map,
    depthTest: false
  })
  materialText.sizeAttenuation = false
  const sprite = new THREE.Sprite(materialText)
  sprite.scale.set(0.1, 0.1, 0.1)
  sprite.center.set(0.5, 0)

  let center = new THREE.Vector3(...item.properties.center, 2)
  sprite.position.copy(center)
  allMesh.add(sprite)
  sprite.visible = false
  sprite.userData.isLabel = true
  sprite.userData.name = item.properties.name
  // 锥点
  const geometryC = new THREE.ConeGeometry(0.5, 1.5, 4)
  var shaderMaterial = new THREE.ShaderMaterial({
    vertexShader: `
		        varying vec2 vUv;
		        void main() {
		            vUv = uv;
		            gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
		        }
		    `,
    fragmentShader: `
		        uniform float gradient; // 定义渐变颜色
				uniform int type;
		        varying vec2 vUv;
		        void main() {
		            float color = smoothstep(0.0, 1.0, vUv.y); // 使用smoothstep函数产生渐变效果
					if (type == 0) {
						gl_FragColor = vec4(vec3(color * 0.1, color * 0.9, 1.0), 1.0); // 将渐变颜色应用到片段
					} else {
						gl_FragColor = vec4(vec3(1.0, color * 0.9, color * 0.1), 1.0);
					}
		        }
		    `,
    uniforms: {
      gradient: { value: 1.0 }, // 定义渐变颜色的强度，范围是0到1
      type: { value: item.properties.name == '河南省' ? 1 : 0 }
    }
  })
  const cone = new THREE.Mesh(geometryC, shaderMaterial)
  cone.castShadow = true
  cone.rotateX(-Math.PI / 2)
  cone.position.copy(center)
  cone.position.y -= 0.5
  cone.userData.isCone = true
  cone.userData.name = item.properties.name
  allMesh.add(cone)
  makeDongHua(cone)
  // 圈
  let circleG = new THREE.Group()
  let circleS = new THREE.Shape()
  circleS.absarc(0, 0, 0.3, 0, Math.PI * 2)
  const geometryCir = new THREE.ShapeGeometry(circleS)
  const materialCir = new THREE.MeshStandardMaterial({
    color: item.properties.name == '河南省' ? '#f96a18' : '#00fff4',
    transparent: true,
    opacity: 1
  })
  const circle1 = new THREE.Mesh(geometryCir, materialCir)
  let circle2 = circle1.clone()
  circle1.position.z += 0.1
  circle2.material = new THREE.MeshStandardMaterial({
    color: item.properties.name == '河南省' ? '#f96a18' : '#00fff4',
    transparent: true,
    opacity: 0.3
  })
  circle2.scale.set(1.5, 1.5, 1)
  circleG.add(circle1)
  circleG.add(circle2)
  circleG.position.copy(center)
  circleG.position.y -= 0.5
  circleG.position.z -= 0.8
  circleG.userData.isCircle = true
  circleG.userData.name = item.properties.name
  allMesh.add(circleG)
  makeDongHua(circleG)
}

const setData = (data) => {
  scene.remove(allMesh)
  allMesh = new THREE.Group()
  data.features.forEach((a, i) => {
    draw(a)
  })
  const box = new THREE.Box3()
  const targetObject = new THREE.Object3D()
  let position = new THREE.Vector3()
  box.setFromObject(allMesh)
  box.getCenter(position)
  position.y += 2
  controls.target.copy(position)
  camera.position.copy(position)
  targetObject.position.copy(position)
  directionalLight.position.copy(position)
  camera.position.y -= 22
  camera.position.z += 35

  directionalLight.target = targetObject
  directionalLight.position.y -= 2
  directionalLight.position.z += 20
  directionalLight.position.x += 10

  scene.add(allMesh)
  scene.add(targetObject)
}

function darkenNonBloomed(obj) {
  if (obj.isMesh && bloomLayer.test(obj.layers) === false) {
    materials[obj.uuid] = obj.material
    obj.material = darkMaterial
  }
  if (obj.isSprite) {
    materials[obj.uuid] = obj.material
    obj.material = darkMaterial
  }
}

function restoreMaterial(obj) {
  if (materials[obj.uuid]) {
    obj.material = materials[obj.uuid]
    delete materials[obj.uuid]
  }
}

let aniId = null
const animate = () => {
  const delta = clock.getDelta()
  aniId = requestAnimationFrame(animate)
  controls.update()

  allMesh.children.forEach((o) => {
    if (o.userData.mixer) o.userData.mixer.update(delta)
  })

  // renderer.render(scene, camera)
  scene.traverse(darkenNonBloomed)
  bloomComposer.render()
  scene.traverse(restoreMaterial)

  finalComposer.render()
}

defineExpose({
  setData,
  chooseQuYu,
  checkMark,
  clearMark
})

onMounted(() => {
  container.value.appendChild(renderer.domElement)
  addEvet()
  animate()
})

onUnmounted(() => {
  cancelAnimationFrame(aniId)
})
</script>

<template>
  <div
    ref="container"
    class="map-box"
    :style="{ width: `${width}px`, height: `${height}px` }"
  ></div>
</template>

<style scoped>
.map-box {
  position: relative;
}
</style>
